﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using WcfMasterSlaveElectionService;

namespace MasterSlaveConsole
{
    class Program
    {
        static void Main(string[] args)
        {
            CatchUnhandledException();

            while (true)
            {
                // start the master/slave functionality
                MasterSlaveFunc func = new MasterSlaveFunc(args[0]);
                func.Run();
                Thread.Sleep(10000);
            }
        }

        #region Error handling

        private static void CatchUnhandledException()
        {
            AppDomain currentDomain = AppDomain.CurrentDomain;
            currentDomain.UnhandledException += new UnhandledExceptionEventHandler(UnhandledExceptionHandler);
        }

        static void UnhandledExceptionHandler(object sender, UnhandledExceptionEventArgs e)
        {
            if (e.ExceptionObject is Exception)
            {
                LogClient logger = new LogClient();
                Exception ex = e.ExceptionObject as Exception;
                logger.LogMessage(ex, "Unhandled Exception occurred.");
            }
        } 
        #endregion
    }

    class MasterSlaveFunc
    {
        private const int RetryLimit = 6;
        private LogClient _logger;
        /// <summary>
        /// WCF host for master slave election
        /// </summary>
        private MasterElectionService _masterElector;
        /// <summary>
        /// Should always be false. If set to true (use method Set()), the Run method will end
        /// </summary>
        private ManualResetEvent _recycleEvent;
        /// <summary>
        /// While false, keeps the masterthread alive. If set to true, the master thread will end
        /// </summary>
        private ManualResetEvent _masterRecycleEvent;
        /// <summary>
        /// Master func thread: the actual work of the master
        /// </summary>
        private Thread _masterFunc;
        private IEndpointProvider _endpointProvider;
        private bool _iAmMaster = false;
        string _id;

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="id"></param>
        public MasterSlaveFunc(string id)
        {
            _id = id;
            _logger = new LogClient();
            _endpointProvider = new EndpointProvider(id);
            _endpointProvider.GetCurrentEndpoints(); // fill endpoints list
            _masterElector = new MasterElectionService(id, _endpointProvider, _logger);

            _recycleEvent = new ManualResetEvent(false);
            _masterRecycleEvent = new ManualResetEvent(false);
        }

        /// <summary>
        /// Initialize the Master Election Serive
        /// </summary>
        private void InstantiateMasterElectionService()
        {
            //instantiates the master election service 
            _masterElector = new MasterElectionService(_id, _endpointProvider, _logger);

            // Subscribe to the NewMaster event
            _masterElector.NewMaster += new EventHandler<MasterSelectionEventArgs>(_masterElector_NewMaster);
            _masterElector.ExpectRole += new EventHandler<ExpectMasterEventArgs>(_masterElector_ExpectRole);

            //Instantiates and starts the instance manager server
            //Listen on master election service
            _masterElector.StartListen();
        }

        /// <summary>
        /// Start running the master/slave functionality.
        /// After instantiating the service and electing a master,
        /// we enter a loop to to if the watchdog is alive
        /// </summary>
        internal void Run()
        {
            InstantiateMasterElectionService();
            _masterElector.EnsureMasterElected();

            int retries = 0;
            while (!_recycleEvent.WaitOne(TimeSpan.FromSeconds(30)))
            {
                if (!_masterElector.IsWatchdogAlive())
                {
                    retries++;
                    _logger.LogMessage("Master watchdog thread is not alive. ");
                }
                else
                {
                    retries = 0;
                    _logger.LogMessage("Health Monitor: Master watchdog thread is alive");
                }
                if (retries >= RetryLimit)
                {
                    _logger.LogMessage("Master watchdog thread is not responding. Recycling role...");
                    _recycleEvent.Set();
                    _masterRecycleEvent.Set();
                }
            }

            _masterElector.StopListen();
        }

        /// <summary>
        /// A new master is elected. Change our role to master or slave
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void _masterElector_NewMaster(object sender, MasterSelectionEventArgs e)
        {
            try
            {
                _logger.LogMessage("New master selected");
                if (_masterElector.IsMaster())
                {
                    _logger.LogMessage("I am master");
                    changeToMasterMode();
                }
                else
                {
                    _logger.LogMessage("I am slave");
                    changeToSlaveMode();
                }
            }
            catch (Exception ex)
            {
                _logger.LogMessage("Error in NewMasterEvent: {0}", ex.Message);
            }
        }

        /// <summary>
        /// Check is out role meets the expectation of the watchdog
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void _masterElector_ExpectRole(object sender, ExpectMasterEventArgs e)
        {
            if (e.ExpectedRoleIsMaster != _iAmMaster)
            {
                _recycleEvent.Set();
                _masterRecycleEvent.Set();
            }
        }

        /// <summary>
        /// Start doing out master thing
        /// </summary>
        private void changeToMasterMode()
        {
            try
            {
                string msg = String.Format("{0} is starting as master", _masterElector.GetHostId);
                _logger.LogMessage(msg);

                // Do your master thingy...
                // If you can't start as master, then Recycle the role:
                //RoleEnvironment.RequestRecycle();
                _iAmMaster = true;
                startFunc();
                _logger.LogMessage("{0} started as master", _masterElector.GetHostId);

            }
            catch (Exception ex)
            {
                _logger.LogMessage("Error in startAsMaster: {0}", ex.Message);
                _recycleEvent.Set();
                _masterRecycleEvent.Set();
            }
        }

        /// <summary>
        /// Start doing out slave thing (which is mostly just not doing out master thing)
        /// </summary>
        private void changeToSlaveMode()
        {
            try
            {
                string msg = string.Format("{0} is starting as slave", _masterElector.GetHostId);
                _logger.LogMessage(msg);

                // Obey, you slave!
                // Always the first node will be the master (1master-nslave implementation)                
                _masterRecycleEvent.Set();
                _iAmMaster = false;

                msg = string.Format("{0} started as slave", _masterElector.GetHostId);
                _logger.LogMessage(msg);
            }
            catch (Exception ex)
            {
                _logger.LogMessage("Error in startAsSlave: {0}", ex.Message);
                _recycleEvent.Set();
                _masterRecycleEvent.Set();
            }
        }

        /// <summary>
        /// Enter doing out master thing, which is logging a message a few times and then die.
        /// this allowes another instance to become the master
        /// </summary>
        private void startFunc()
        {
            _masterRecycleEvent.Reset();
            
            _masterFunc = new Thread(delegate()
            {
                int count = 0;
                _logger.LogMessage("Starting Master func thread");
                while (!_masterRecycleEvent.WaitOne(TimeSpan.FromSeconds(10)))
                {
                    _logger.LogMessage("Doing work...");
                    if (count >= 6)
                    {
                        _masterRecycleEvent.Set();
                        
                    }
                    count++;
                }
                _iAmMaster = false;
                _logger.LogMessage("Ending Master func thread");
                _recycleEvent.Set();
            });

            _masterFunc.Start();
        }
    }
}
